﻿using System.ComponentModel;
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using System.Windows;
using System.Windows.Controls;

namespace WpfGenetic
{
    public static class GeneticConfigCreator
    {
        /// <summary>
        /// Пустой конструктор, создаёт конфигурацию по умолчанию
        /// </summary>
        public static GeneticConfiguration Factory() 
        { 
            return new GeneticConfiguration();
        }

        /// <summary>
        /// Конструктор принимает путь к файлу JSON, десериализует его и заполняет поля
        /// Файл JSON должен соответствовать полям класса!!!
        /// </summary>
        /// <param name="jsonPath"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static GeneticConfiguration Factory(string jsonPath)
        {
            GeneticConfiguration? config;
            if (jsonPath is null || jsonPath.Length == 0)
            {
                MessageBox.Show("Путь к файлу конфигурации пустой!\nБыла загружена конфигурация по умолчанию.");
                config = new GeneticConfiguration();
                return config;
            }

            try
            {
                string json = File.ReadAllText(jsonPath);

                if (json is null || json.Length == 0)
                {
                    MessageBox.Show("Файл конфигурации пустой!\nБыла загружена конфигурация по умолчанию.");
                    config = new GeneticConfiguration();
                    return config;
                }

                config = JsonSerializer.Deserialize<GeneticConfiguration>(json);
            }
            catch(FileNotFoundException)
            {
                MessageBox.Show("Путь к файлу конфигурации не верный или файл отсутствует!\nБыла загружена конфигурация по умолчанию.");
                config = new GeneticConfiguration();
                return config;
            }
            catch(JsonException)
            {
                MessageBox.Show("Содержимое файла конфигурации не валидно!\nБыла загружена конфигурация по умолчанию.");
                config = new GeneticConfiguration();
                return config;
            }

            config ??= new GeneticConfiguration();

            return config;
        }
    }

    // Параметры генетического алгоритма
    public class GeneticConfiguration: ICloneable, IDataErrorInfo
    {
        // Информация
        public int CIPHER_TYPE { get; set; } = 0;                      // Тип шифра: 0 - шифр Виженера 1 - Перестановочный шифр
        public int POPULATION_SIZE { get; set; } = 1000;               // Количество индивидуумов в популяции
        public int MAX_REPETITION_OF_INDIVIDUALS { get; set; } = 2;    // Максимальное количество одинаковых особей в популяции
        public int MAX_GENERATIONS { get; set; } = 2000;               // Максимальное количество поколений
        public int CONSOLE_WRITTEN_COUNT { get; set; } = 2;            // Количество выводимых самых лучших особей
        public int COUNT_OF_MAX_SAVE { get; set; } = 2;                // Количество лучших особей, которые всегда остаются в популяции и не участвуют в турнире

        // Характеристики ключей
        // Характеристики ключа шифра Виженера
        public int MIN_START_KEY_LENGTH { get; set; } = 1;             // Минимальная длина ключа при создании популяции
        public int MAX_START_KEY_LENGTH { get; set; } = 10;            // Максимальная длина ключа при создании популяции
        public int RANK_KEY_LENGTH { get; set; } = 10;                   // Максимальная длина ключа в принципе (указывается кол-во разрядов в двоичной системе)
        // Характеристики ключей Перестановочного Шифра
        public int HORIZONTAL_MIN_KEY_LENGTH { get; set; } = 1;         // Минимальная длина горизонтального ключа при создании популяции
        public int HORIZONTAL_MAX_KEY_LENGTH { get; set; } = 10;        // Максимальная длина горизонтального ключа при создании популяции
        public int RANK_HORIZONTAL_KEY_LENGTH { get; set; } = 10;        // Максимальная длина горизонтального ключа в принципе (указывается кол-во разрядов в двоичной системе)

        public int VERTICAL_MIN_KEY_LENGTH { get; set; } = 1;           // Минимальная длина вертикального ключа при создании популяции
        public int VERTICAL_MAX_KEY_LENGTH { get; set; } = 10;          // Максимальная длина вертикального ключа при создании популяции
        public int RANK_VERTICAL_KEY_LENGTH { get; set; } = 10;          // Максимальная длина вертикального ключа в принципе (указывается кол-во разрядов в двоичной системе)

        // Фитнесс функция
        public int? BIGRAMS_TYPE { get; set; } = 2;                    // Тип использующейся биграммы: 1 - моя таблица биграмм; 2 - мои биграммы по Достоевскому; null - Не использовать биграммы в вычислениях
        public int? TRIGRAMS_TYPE { get; set; } = 2;                   // Тип использующейся триграммы: 1 - моя таблица биграмм; 2 - мои биграммы по Достоевскому; null - Не использовать триграммы в вычислениях
        public bool INDEX_OF_MATCHES_FLAG { get; set; } = true;        // Использовать индекс совпадений (true) или нет (false)

        // Отбор
        public int SIZE_TOURNAMENT { get; set; } = 2;                  // Численность турнира

        // Скрещивание
        public double P_CROSSOVER { get; set; } = 50;                  // Вероятность скрещивания двух особей
        public double P_KEY_CROSSOVER { get; set; } = 50;              // Вероятность того, что ребёнок получит символ первого родителя

        // Мутация
        public double P_MUTATION { get; set; } = 70;                   // Вероятность мутации индивида
        public double P_KEY_MUTATION { get; set; } = 80;               // Вероятность того, что при мутации измениться символ ключа, применяется к каждому символу
        public double P_LENGTH_MUTATION { get; set; } = 40;            // Вероятность того, что при мутации измениться длина ключа
        public double P_RAISE_MUTATION { get; set; } = 50;             // Вероятность того, что при изменении длины ключа она увеличится на 1 (дополнительный символ берётся из алфавита и встаёт в конец ключа), иначе у ключа удаляется последний символ (если изначально длина 1 ничего не происходит)

        // Алфавиты
        public bool USING_ALPHABET_FOR_GENERATION { get; set; } = false;    // Использовать дополнительный алфавит (true) или нет (false)
        public string Alphabet { get; set; } = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя ";    // Основной алфавит
        public string AlphabetSpecial { get; set; } = "абвгдеёжзи";       // Алфавит, применяемый в создании индивидов и мутации

        public string Error
        {
            get
            {
                return null!;
            }
        }

        public string this[string columnName]
        {
            get
            {
                string error = String.Empty;
                switch (columnName)
                {
                    // Информация
                    case "CIPHER_TYPE":
                        if (CIPHER_TYPE < 1)
                        {
                            error = "Не валидное значение номера шифра";
                        }
                        break;
                    case "POPULATION_SIZE":
                        if (POPULATION_SIZE < 1)
                        {
                            error = "Популяция не может быть меньше 1 особи";
                        }
                        break;
                    case "MAX_REPETITION_OF_INDIVIDUALS":
                        if (MAX_REPETITION_OF_INDIVIDUALS < 0)
                        {
                            error = "Максимальное количество повторяющихся особей не может быть меньше 0";
                        }
                        break;
                    case "MAX_GENERATIONS":
                        if (MAX_GENERATIONS < 1)
                        {
                            error = "Максимальное количество поколений не может быть меньше 1";
                        }
                        break;
                    case "CONSOLE_WRITTEN_COUNT":
                        if (CONSOLE_WRITTEN_COUNT < 0)
                        {
                            error = "Количество особей, о которых выводиться информация, не может быть меньше 1 особи";
                        }
                        break;
                    case "COUNT_OF_MAX_SAVE":
                        if (COUNT_OF_MAX_SAVE < 0)
                        {
                            error = "Количество особей, о которых выводиться информация, не может быть меньше 1";
                        }
                        break;
                    // Характеристики ключей
                    // Характеристики ключа шифра Виженера
                    case "MIN_START_KEY_LENGTH":
                        if (MIN_START_KEY_LENGTH < 1)
                        {
                            error = "Минимальная длина создания первых ключей не может быть меньше 1";
                        }
                        break;
                    case "MAX_START_KEY_LENGTH":
                        if (MAX_START_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина создания первых ключей не может быть меньше 1";
                        }
                        break;
                    case "RANK_KEY_LENGTH":
                        if (RANK_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина ключей не может быть меньше 1";
                        }
                        if (RANK_KEY_LENGTH > 31)
                        {
                            error = "Максимальная длина ключей не может быть больше 31";
                        }
                        break;
                    // Характеристики ключей Перестановочного Шифра
                    case "HORIZONTAL_MIN_KEY_LENGTH":
                        if (HORIZONTAL_MIN_KEY_LENGTH < 1)
                        {
                            error = "Минимальная длина создания первых горизонтальных ключей не может быть меньше 1";
                        }
                        break;
                    case "HORIZONTAL_MAX_KEY_LENGTH":
                        if (HORIZONTAL_MAX_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина создания первых горизонтальных ключей не может быть меньше 1";
                        }
                        break;
                    case "RANK_HORIZONTAL_KEY_LENGTH":
                        if (RANK_HORIZONTAL_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина горизонтальных ключей не может быть меньше 1";
                        }
                        if (RANK_HORIZONTAL_KEY_LENGTH > 31)
                        {
                            error = "Максимальная длина горизонтальных ключей не может быть больше 31";
                        }
                        break;
                    case "VERTICAL_MIN_KEY_LENGTH":
                        if (VERTICAL_MIN_KEY_LENGTH < 1)
                        {
                            error = "Минимальная длина создания первых вертикальных ключей не может быть меньше 1";
                        }
                        break;
                    case "VERTICAL_MAX_KEY_LENGTH":
                        if (VERTICAL_MAX_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина создания первых вертикальных ключей не может быть меньше 1";
                        }
                        break;
                    case "RANK_VERTICAL_KEY_LENGTH":
                        if (RANK_VERTICAL_KEY_LENGTH < 1)
                        {
                            error = "Максимальная длина вертикальных ключей не может быть меньше 1";
                        }
                        if (RANK_VERTICAL_KEY_LENGTH > 31)
                        {
                            error = "Максимальная длина вертикальных ключей не может быть больше 31";
                        }
                        break;

                    // Фитнесс функция
                    case "BIGRAMS_TYPE":
                        if (BIGRAMS_TYPE is not null && BIGRAMS_TYPE < 1)
                        {
                            error = "Тип биграммы не может быть меньше 1";
                        }
                        break;
                    case "TRIGRAMS_TYPE":
                        if (TRIGRAMS_TYPE is not null && TRIGRAMS_TYPE < 1)
                        {
                            error = "Тип триграммы не может быть меньше 1";
                        }
                        break;
                    // Отбор
                    case "SIZE_TOURNAMENT":
                        if (SIZE_TOURNAMENT < 1)
                        {
                            error = "Численность турнира не может быть меньше 1";
                        }
                        break;
                    // Скрещивание
                    case "P_CROSSOVER":
                        if (P_CROSSOVER < 0 || P_CROSSOVER > 100)
                        {
                            error = "Вероятность скрещивания должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    case "P_KEY_CROSSOVER":
                        if (P_KEY_CROSSOVER < 0 || P_KEY_CROSSOVER > 100)
                        {
                            error = "Вероятность получения символа ключа от родителя '1' должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    // Мутация
                    case "P_MUTATION":
                        if (P_MUTATION < 0 || P_MUTATION > 100)
                        {
                            error = "Вероятность мутации особи должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    case "P_KEY_MUTATION":
                        if (P_KEY_MUTATION < 0 || P_KEY_MUTATION > 100)
                        {
                            error = "Вероятность мутации символа ключа должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    case "P_LENGTH_MUTATION":
                        if (P_LENGTH_MUTATION < 0 || P_LENGTH_MUTATION > 100)
                        {
                            error = "Вероятность мутации длины ключа должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    case "P_RAISE_MUTATION":
                        if (P_RAISE_MUTATION < 0 || P_RAISE_MUTATION > 100)
                        {
                            error = "Вероятность увеличения длины ключа должна быть в диапазоне от 0 до 100";
                        }
                        break;
                    // Алфавит
                    case "Alphabet":
                        if (Alphabet == "" || Alphabet is null)
                        {
                            error = "Алфавит не может быть пустым";
                        }
                        break;
                    case "AlphabetSpecial":
                        if (AlphabetSpecial == "" || AlphabetSpecial is null)
                        {
                            error = "Алфавит не может быть пустым";
                        }
                        break;
                }
                return error;
            }
        }

        // Кэширование экземпляра JsonSerializerOptions для оптимизации
        private static readonly JsonSerializerOptions options = new()
        {
            WriteIndented = true,
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic)
        };

        /// <summary>
        /// Сохранение текущей конфигурации в файл
        /// </summary>
        /// <param name="jsonPath"></param>
        /// <exception cref="ArgumentException"></exception>
        public void SaveJsonInFile(string jsonPath)
        {
            if (jsonPath is null || jsonPath.Length == 0)
            {
                MessageBox.Show("Путь к файлу конфигурации пустой!");
                return;
            }

            string json = JsonSerializer.Serialize(this, options);

            if (!File.Exists(jsonPath))
            {
                MessageBox.Show("Файл конфигурации с таким именем отсутствует!\nБыл создан новый.");
            }

            File.WriteAllText(jsonPath, json);
        }

        /// <summary>
        /// Полное копирование объекта
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            return MemberwiseClone();
        }

        /// <summary>
        /// Полностью копирует состояние переданной конфигурации в себя
        /// </summary>
        /// <param name="conf"></param>
        public void CopyIn(GeneticConfiguration conf)
        {
            CIPHER_TYPE = conf.CIPHER_TYPE;
            POPULATION_SIZE = conf.POPULATION_SIZE;
            MAX_REPETITION_OF_INDIVIDUALS = conf.MAX_REPETITION_OF_INDIVIDUALS;
            MAX_GENERATIONS = conf.MAX_GENERATIONS;
            CONSOLE_WRITTEN_COUNT = conf.CONSOLE_WRITTEN_COUNT;
            COUNT_OF_MAX_SAVE = conf.COUNT_OF_MAX_SAVE;
            MIN_START_KEY_LENGTH = conf.MIN_START_KEY_LENGTH;
            MAX_START_KEY_LENGTH = conf.MAX_START_KEY_LENGTH;
            RANK_KEY_LENGTH = conf.RANK_KEY_LENGTH;
            HORIZONTAL_MIN_KEY_LENGTH = conf.HORIZONTAL_MIN_KEY_LENGTH;
            HORIZONTAL_MAX_KEY_LENGTH = conf.HORIZONTAL_MAX_KEY_LENGTH;
            RANK_HORIZONTAL_KEY_LENGTH = conf.RANK_HORIZONTAL_KEY_LENGTH;
            VERTICAL_MIN_KEY_LENGTH = conf.VERTICAL_MIN_KEY_LENGTH;
            VERTICAL_MAX_KEY_LENGTH = conf.VERTICAL_MAX_KEY_LENGTH;
            RANK_VERTICAL_KEY_LENGTH = conf.RANK_VERTICAL_KEY_LENGTH;
            BIGRAMS_TYPE = conf.BIGRAMS_TYPE;
            TRIGRAMS_TYPE = conf.TRIGRAMS_TYPE;
            INDEX_OF_MATCHES_FLAG = conf.INDEX_OF_MATCHES_FLAG;
            SIZE_TOURNAMENT = conf.SIZE_TOURNAMENT;
            P_CROSSOVER = conf.P_CROSSOVER;
            P_KEY_CROSSOVER = conf.P_KEY_CROSSOVER;
            P_MUTATION = conf.P_MUTATION;
            P_KEY_MUTATION = conf.P_KEY_MUTATION;
            P_LENGTH_MUTATION = conf.P_LENGTH_MUTATION;
            P_RAISE_MUTATION = conf.P_RAISE_MUTATION;
            USING_ALPHABET_FOR_GENERATION = conf.USING_ALPHABET_FOR_GENERATION;
            Alphabet = new string(conf.Alphabet);
            AlphabetSpecial = new string(conf.AlphabetSpecial);
        }
    }
}